package commons.base.excel;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import commons.base.excel.annotation.Excel;
import commons.base.excel.annotation.ExcelColumn;
import commons.base.excel.annotation.ExcelInit;
import commons.base.excel.event.ExcelEventParser;
import commons.base.excel.event.ExcelEventParser.SimpleSheetContentsHandler;
import commons.base.util.ReflectUtil;

public class ExcelManager {
	
	private static Path resourcePath;
	
	static{
		resourcePath = Paths.get("").resolve("src/main/resources").toAbsolutePath();
	}
	
	private static Map<Class<? extends BaseExcelTemplate<?>>, BaseExcelTemplate<?>> cache = new ConcurrentHashMap<>();

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static <T extends BaseExcelTemplate<?>> T get(Class<T> clazz) {
		if(cache.containsKey(clazz)){
			return (T)cache.get(clazz);
		}
		
		T t = null;
		
		synchronized (clazz) {
			if(cache.containsKey(clazz)){
				return (T)cache.get(clazz);
			}
			
			try {
				t = clazz.newInstance();
				List list = load(clazz, t.getModelType());
				t.setItems(list);
				
				cache.put(clazz, t);
			} catch (Throwable e) {
				throw new RuntimeException(e.getMessage(), e);
			}
		}
		
		return t;
	}
	private static <T> List<T> load(Class<?> templateClazz, Class<T> clazz) throws Throwable{
//		return load(clazz, getExcelFile(templateClazz), getIgnoreRows(templateClazz));
		return loadByEventDrive(clazz, getExcelFile(templateClazz), getIgnoreRows(templateClazz));
	}
	public static <T> List<T> load(Class<T> clazz, String excelFile, int ignoreRows) throws Throwable{
		List<T> list = new ArrayList<T>();
		
		ExcelParser excelParser = null;
		try {
			excelParser = new ExcelParser(resourcePath.resolve(excelFile).toAbsolutePath().toString());
			
			int rowNumber = 1;
			List<String> fields = null;
			while(excelParser.hasNext()){
				List<String> row = excelParser.next();
				
				if(rowNumber < ignoreRows){
					// 第一行是标题. 忽略
				}else if(rowNumber == ignoreRows){
					// 第二行是字段名
					fields = row;
				}else{
					// 数据
					list.add(fill(clazz, genFillMap(fields, row)));
				}
				
				rowNumber++;
			}
		} finally {
			if(excelParser != null) excelParser.close();
		}
		
		return list;
	}
	
	public static <T> List<T> loadByEventDrive(Class<T> clazz, String excelFile, final int ignoreRows) throws Throwable{
		final List<T> list = new ArrayList<T>();
		
		new ExcelEventParser(resourcePath.resolve(excelFile).toAbsolutePath().toString()).setHandler(new SimpleSheetContentsHandler(){
			private List<String> fields;
			
			@Override
			public void endRow(int rowNum) {
				if((rowNum+1) < ignoreRows){
					// 第一行是标题. 忽略
				}else if((rowNum+1) == ignoreRows){
					// 第二行是字段名
					fields = row;
				}else{
					// 数据
					try {
						list.add(fill(clazz, genFillMap(fields, row)));
					} catch (Throwable e) {
						e.printStackTrace();
					}
				}
			}
		}).parse();
		
		return list;
	}
	
	private static String getExcelFile(Class<?> clazz){
		if(clazz.isAnnotationPresent(Excel.class)){
			String name = clazz.getAnnotation(Excel.class).name();
			if((name != null) && (name.trim().length() > 0)){
				return name;
			}
		}
		
		return clazz.getSimpleName() + ".xlsx";
	}
	private static int getIgnoreRows(Class<?> clazz){
		if(clazz.isAnnotationPresent(Excel.class)){
			return clazz.getAnnotation(Excel.class).ignoreRows();
		}
		
		return ExcelConstants.DEFAULT_IGNORE_ROWS;
	}
	
	public static <T> List<T> get(String excelFile, Class<T> clazz) throws Throwable{
		return load(clazz, excelFile, ExcelConstants.DEFAULT_IGNORE_ROWS);
	}
	
	private static Map<String, String> genFillMap(List<String> fields, List<String> data){
		Map<String, String> map = new HashMap<String, String>();
		
		for(int i=0; i<fields.size(); i++){
			if(i < data.size()){
				map.put(fields.get(i), data.get(i));
			}else{
				map.put(fields.get(i), null);
			}
		}
		
		return map;
	}
	
	private static MethodHandles.Lookup lookup = MethodHandles.lookup();
	
	private static <T> T fill(Class<T> clazz, Map<String, String> map)throws Throwable{
		T bean = clazz.newInstance();
		
		Field[] fields = clazz.getDeclaredFields();
		if(fields != null){
			for(Field f : fields){
				if(map.containsKey(f.getName())){
					f.setAccessible(true);
					MethodHandle mh = lookup.unreflectSetter(f);
					
					String rawValue = map.get(f.getName());
					Object value = null;
					
					if(hasCustomParser(f)){
						// 使用自定义解析器解析
						value = parseCustom(rawValue, f);
					}else if(List.class.isAssignableFrom(f.getType())){
						value = parseList(rawValue, f);
					}else if(Map.class.isAssignableFrom(f.getType())){
						value = parseMap(rawValue, f);
					}else if(!ReflectUtil.isBasicType(f.getType())){
						value = parseObject(rawValue, f);
					}else {
						value = ReflectUtil.parseBasicObject(rawValue, f.getType());
					}
					
					mh.invoke(bean, value);
				}
			}
		}
		
		initBean(bean);
		
		return bean;
	}
	
	private static void initBean(Object bean) throws Throwable{
		MethodHandle mh = ReflectUtil.getFirstMethodByAnnotation(bean.getClass(), ExcelInit.class);
		if(mh != null){
			mh.invoke(bean);
		}
	}
	
	private static boolean hasCustomParser(Field f){
		if(f.isAnnotationPresent(ExcelColumn.class)){
			return f.getAnnotation(ExcelColumn.class).parser() != NoneColumnParser.class;
		}
		return false;
	}
	private static Object parseCustom(String value, Field f) throws InstantiationException, IllegalAccessException{
		ExcelColumnParser parser = f.getAnnotation(ExcelColumn.class).parser().newInstance();
		return parser.parse(value);
	}
	
	private static List<Object> parseList(String value, Field f) throws Throwable{
		if((value == null) || (value.trim().length() == 0)){
			return null;
		}
		value = value.trim();
		
		Class<?> clazz = ReflectUtil.getParameterizedType(f, 0);
		boolean isBasicType = ReflectUtil.isBasicType(clazz);
		
		String[] valList = value.split(getListDelimiter(f));
		List<Object> list = new ArrayList<Object>();
		
		for(String val : valList){
			if(!isBasicType){
				list.add(parseObject(val, clazz, getObjectDelimiter(f), getFieldDelimiter(f)));
			}else{
				list.add(ReflectUtil.parseBasicObject(val, clazz));
			}
		}
		
		return list;
	}
	private static String getListDelimiter(Field f){
		if(f.isAnnotationPresent(ExcelColumn.class)){
			return f.getAnnotation(ExcelColumn.class).listDelimiter();
		}
		
		return ExcelConstants.LIST_DELIMITER;
	}
	private static String getObjectDelimiter(Field f){
		if(f.isAnnotationPresent(ExcelColumn.class)){
			return f.getAnnotation(ExcelColumn.class).objectDelimiter();
		}
		
		return ExcelConstants.OBJECT_DELIMITER;
	}
	private static String getFieldDelimiter(Field f){
		if(f.isAnnotationPresent(ExcelColumn.class)){
			return f.getAnnotation(ExcelColumn.class).fieldDelimiter();
		}
		
		return ExcelConstants.FIELD_DELIMITER;
	}
	
	private static Map<Object, Object> parseMap(String value, Field f){
		if((value == null) || (value.trim().length() == 0)){
			return null;
		}
		value = value.trim();
		
		Class<?> keyType = ReflectUtil.getParameterizedType(f, 0);
		Class<?> valueType = ReflectUtil.getParameterizedType(f, 1);
		
		String[] entries = value.split(getObjectDelimiter(f));
		
		Map<Object, Object> map = new HashMap<Object, Object>();
		for(String entry : entries){
			String[] kv = entry.split(getFieldDelimiter(f));
			
			map.put(ReflectUtil.parseBasicObject(kv[0], keyType), ReflectUtil.parseBasicObject(kv[1], valueType));
		}
		
		return map;
	}
	
	private static Object parseObject(String value, Field f) throws Throwable{
		return parseObject(value, f.getType(), getObjectDelimiter(f), getFieldDelimiter(f));
	}

	private static Object parseObject(String value, Class<?> clazz, String propertyDelimiter, String nvDelimiter) throws Throwable{
		if((value == null) || (value.trim().length() == 0)){
			return null;
		}
		value = value.trim();
		
		String[] propertys = value.split(propertyDelimiter);
		
		Map<String, String> map = new HashMap<String, String>();
		for(String property : propertys){
			String[] nv = property.split(nvDelimiter);
			map.put(nv[0], nv[1]);
		}
		
		return fill(clazz, map);
	}
}
